package com.gitee.wsl.data.input.viewmodel

import com.gitee.wsl.data.input.EventStrategy
import com.gitee.wsl.data.input.FilteredInputHandler
import com.gitee.wsl.data.input.InputFilter
import com.gitee.wsl.data.input.InputHandler
import com.gitee.wsl.data.input.InputStrategy
import com.gitee.wsl.data.input.interceptor.BallastInterceptor
import com.gitee.wsl.data.input.strategy.BufferedEventStrategy
import com.gitee.wsl.data.input.strategy.LifoInputStrategy
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

/**
 * This class collects all the configurable properties of a [InputModel].
 */
 interface InputModelConfiguration<Inputs : Any, Events : Any, State : Any> {
     val initialState: State
     val inputHandler: InputHandler<Inputs, Events, State>

     val interceptors: List<BallastInterceptor<Inputs, Events, State>>

     val inputStrategy: InputStrategy<Inputs, Events, State>
     val eventStrategy: EventStrategy<Inputs, Events, State>

     val inputsDispatcher: CoroutineDispatcher
     val eventsDispatcher: CoroutineDispatcher
     val sideJobsDispatcher: CoroutineDispatcher
     val interceptorDispatcher: CoroutineDispatcher

     val name: String
    // val logger: BallastLogger

     data class Builder(
         var name: String? = null,
         var initialState: Any? = null,
         var inputHandler: InputHandler<*, *, *>? = null,
        @Deprecated("InputFilter is no longer used by the VM configuration. Pass the filter to the InputStrategy instead.")
         var filter: InputFilter<*, *, *>? = null,
         val interceptors: MutableList<BallastInterceptor<*, *, *>> = mutableListOf(),

         var inputStrategy: InputStrategy<*, *, *> = LifoInputStrategy(),
         var eventStrategy: EventStrategy<*, *, *> = BufferedEventStrategy(),

         var inputsDispatcher: CoroutineDispatcher = Dispatchers.Default,
         var eventsDispatcher: CoroutineDispatcher = Dispatchers.Default,
         var sideJobsDispatcher: CoroutineDispatcher = Dispatchers.Default,
         var interceptorDispatcher: CoroutineDispatcher = Dispatchers.Default,

        // var logger: (String) -> BallastLogger = { NoOpLogger() },
    )

     data class TypedBuilder<Inputs : Any, Events : Any, State : Any>(
         var name: String?,
         var initialState: State?,
         var inputHandler: InputHandler<Inputs, Events, State>?,
         val interceptors: MutableList<BallastInterceptor<Inputs, Events, State>>,

         var inputStrategy: InputStrategy<Inputs, Events, State>?,
         var eventStrategy: EventStrategy<Inputs, Events, State>?,

         var inputsDispatcher: CoroutineDispatcher,
         var eventsDispatcher: CoroutineDispatcher,
         var sideJobsDispatcher: CoroutineDispatcher,
         var interceptorDispatcher: CoroutineDispatcher,

        // var logger: (String) -> BallastLogger,
    )
}


/**
 * A default implementation of [InputModelConfiguration] produced by [InputModelConfiguration.Builder].
 * Using the builder is not necessary in most cases; you can create your configuration directly with
 * [DefaultViewModelConfiguration], or you can implement [InputModelConfiguration] yourself to provide a different
 * configuration builder pattern for your application.
 */
 class DefaultViewModelConfiguration<Inputs : Any, Events : Any, State : Any>(
    override val initialState: State,
    override val inputHandler: InputHandler<Inputs, Events, State>,
    override val interceptors: List<BallastInterceptor<Inputs, Events, State>>,
    override val inputStrategy: InputStrategy<Inputs, Events, State>,
    override val eventStrategy: EventStrategy<Inputs, Events, State>,
    override val inputsDispatcher: CoroutineDispatcher,
    override val eventsDispatcher: CoroutineDispatcher,
    override val sideJobsDispatcher: CoroutineDispatcher,
    override val interceptorDispatcher: CoroutineDispatcher,
    override val name: String,
) : InputModelConfiguration<Inputs, Events, State>

//  APIs for untyped Builder
// ---------------------------------------------------------------------------------------------------------------------

/**
 * Create a default [InputModelConfiguration] from a [InputModelConfiguration.Builder].
 */
 fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.Builder.build(
): InputModelConfiguration<Inputs, Events, State> {
    val vmName = name ?: "$inputHandler-vm"
    @Suppress("DEPRECATION")
    return DefaultViewModelConfiguration<Inputs, Events, State>(
        initialState = initialState.requireTyped("initialState"),
        inputHandler = inputHandler.requireTyped("inputHandler"),
        interceptors = interceptors.mapAsTyped(),
        inputStrategy = inputStrategy.requireTyped("inputStrategy"),
        eventStrategy = eventStrategy.requireTyped("eventStrategy"),
        inputsDispatcher = inputsDispatcher,
        eventsDispatcher = eventsDispatcher,
        sideJobsDispatcher = sideJobsDispatcher,
        interceptorDispatcher = interceptorDispatcher,
        name = vmName,
    )
}

/**
 * Create a default [InputModelConfiguration] from a [InputModelConfiguration.Builder].
 */
 fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.Builder.typedBuilder(
): InputModelConfiguration.TypedBuilder<Inputs, Events, State> {
    val vmName = name ?: "$inputHandler-vm"
    return InputModelConfiguration.TypedBuilder<Inputs, Events, State>(
        initialState = initialState.requireTypedIfPresent("initialState"),
        inputHandler = inputHandler.requireTypedIfPresent("inputHandler"),
        interceptors = interceptors.mapAsTyped<Inputs, Events, State>().toMutableList(),
        inputStrategy = inputStrategy.requireTypedInputStrategyIfPresent(),
        eventStrategy = eventStrategy.requireTypedEventStrategyIfPresent(),
        inputsDispatcher = inputsDispatcher,
        eventsDispatcher = eventsDispatcher,
        sideJobsDispatcher = sideJobsDispatcher,
        interceptorDispatcher = interceptorDispatcher,
        name = vmName,
    )
}

/**
 * Set all [CoroutineDispatcher]s in your ViewModel. For convenience, you can set only the [inputsDispatcher] to use
 * that for all dispatchers, or set the dispatcher for each feature individually.
 */
 fun InputModelConfiguration.Builder.dispatchers(
    inputsDispatcher: CoroutineDispatcher,
    eventsDispatcher: CoroutineDispatcher = inputsDispatcher,
    sideJobsDispatcher: CoroutineDispatcher = inputsDispatcher,
    interceptorDispatcher: CoroutineDispatcher = inputsDispatcher,
): InputModelConfiguration.Builder = apply {
    this.inputsDispatcher = inputsDispatcher
    this.eventsDispatcher = eventsDispatcher
    this.sideJobsDispatcher = sideJobsDispatcher
    this.interceptorDispatcher = interceptorDispatcher
}

/**
 * Add a [BallastInterceptor] to the [InputModelConfiguration.Builder].
 */
 operator fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.Builder.plusAssign(
    interceptor: BallastInterceptor<Inputs, Events, State>
) {
    this.interceptors += interceptor
}

/**
 * Add many [BallastInterceptor]s to the [InputModelConfiguration.Builder].
 */
 operator fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.Builder.plusAssign(
    interceptors: Iterable<BallastInterceptor<Inputs, Events, State>>
) {
    this.interceptors += interceptors
}

/**
 * Set the required properties of the Builder in a type-safe way, making sure the relevant features are all
 * type-compatible with each other even though the builder itself is untyped. Returns a fully-built
 * [InputModelConfiguration].
 */
 fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.Builder.withViewModel(
    initialState: State,
    inputHandler: InputHandler<Inputs, Events, State>,
    name: String? = this.name,
): InputModelConfiguration.TypedBuilder<Inputs, Events, State> =
    this
        .typedBuilder<Inputs, Events, State>()
        .apply {
            this.initialState = initialState
            this.inputHandler = inputHandler
            this.name = name
        }

/**
 * Set the required properties of the Builder in a type-safe way, making sure the relevant features are all
 * type-compatible with each other even though the builder itself is untyped. Returns a fully-built
 * [InputModelConfiguration].
 */
@Deprecated("InputFilter is no longer used by the VM configuration. Pass the filter to the InputStrategy instead.")
@Suppress("UNUSED_PARAMETER")
 fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.Builder.withViewModel(
    initialState: State,
    inputHandler: InputHandler<Inputs, Events, State>,
    filter: InputFilter<Inputs, Events, State>?,
    name: String? = this.name,
): InputModelConfiguration.TypedBuilder<Inputs, Events, State> {
    throw NotImplementedError("InputFilter is no longer used by the VM configuration. Pass the filter to the InputStrategy instead.")
}

/**
 * Set the required properties of the Builder in a type-safe way, making sure the relevant features are all
 * type-compatible with each other even though the builder itself is untyped. Returns a fully-built
 * [InputModelConfiguration].
 */
@Deprecated("InputFilter is no longer used by the VM configuration. Pass the filter to the InputStrategy instead.")
@Suppress("UNUSED_PARAMETER")
 fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.Builder.withViewModel(
    initialState: State,
    inputHandler: FilteredInputHandler<Inputs, Events, State>,
    name: String? = this.name,
): InputModelConfiguration.TypedBuilder<Inputs, Events, State> {
    throw NotImplementedError("InputFilter is no longer used by the VM configuration. Pass the filter to the InputStrategy instead.")
}

// Internal Helpers
// ---------------------------------------------------------------------------------------------------------------------


@Suppress("UNCHECKED_CAST")
internal fun <T : Any> Any?.requireTyped(name: String): T {
    if (this == null) error("$name required")
    return this as T
}

@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER")
internal fun <T : Any> Any?.requireTypedIfPresent(name: String): T? {
    if (this == null) return null
    return this as T
}

@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER")
internal fun <Inputs : Any, Events : Any, State : Any> InputStrategy<*, *, *>?.requireTypedInputStrategyIfPresent(): InputStrategy<Inputs, Events, State>? {
    if (this == null) return null
    return this as InputStrategy<Inputs, Events, State>
}

@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER")
internal fun <Inputs : Any, Events : Any, State : Any> EventStrategy<*, *, *>?.requireTypedEventStrategyIfPresent(): EventStrategy<Inputs, Events, State>? {
    if (this == null) return null
    return this as EventStrategy<Inputs, Events, State>
}

@Suppress("UNCHECKED_CAST")
internal fun <Inputs : Any, Events : Any, State : Any> List<BallastInterceptor<*, *, *>>.mapAsTyped(
): List<BallastInterceptor<Inputs, Events, State>> {
    return this.map { it as BallastInterceptor<Inputs, Events, State> }
}


//  APIs for typed Builder
// ---------------------------------------------------------------------------------------------------------------------

/**
 * Create a default [InputModelConfiguration] from a [InputModelConfiguration.Builder].
 */
fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.TypedBuilder<Inputs, Events, State>.build(
): InputModelConfiguration<Inputs, Events, State> {
    val vmName = name ?: "$inputHandler-vm"
    @Suppress("DEPRECATION")
    return DefaultViewModelConfiguration<Inputs, Events, State>(
        initialState = initialState.requireTyped("initialState"),
        inputHandler = inputHandler.requireTyped("inputHandler"),
        interceptors = interceptors.mapAsTyped(),
        inputStrategy = inputStrategy.requireTyped("inputStrategy"),
        eventStrategy = eventStrategy.requireTyped("eventStrategy"),
        inputsDispatcher = inputsDispatcher,
        eventsDispatcher = eventsDispatcher,
        sideJobsDispatcher = sideJobsDispatcher,
        interceptorDispatcher = interceptorDispatcher,
        name = vmName,
    )
}

/**
 * Set all [CoroutineDispatcher]s in your ViewModel. For convenience, you can set only the [inputsDispatcher] to use
 * that for all dispatchers, or set the dispatcher for each feature individually.
 */
 fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.TypedBuilder<Inputs, Events, State>.dispatchers(
    inputsDispatcher: CoroutineDispatcher,
    eventsDispatcher: CoroutineDispatcher = inputsDispatcher,
    sideJobsDispatcher: CoroutineDispatcher = inputsDispatcher,
    interceptorDispatcher: CoroutineDispatcher = inputsDispatcher,
): InputModelConfiguration.TypedBuilder<Inputs, Events, State> = apply {
    this.inputsDispatcher = inputsDispatcher
    this.eventsDispatcher = eventsDispatcher
    this.sideJobsDispatcher = sideJobsDispatcher
    this.interceptorDispatcher = interceptorDispatcher
}

/**
 * Add a [BallastInterceptor] to the [InputModelConfiguration.Builder].
 */
 operator fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.TypedBuilder<Inputs, Events, State>.plusAssign(
    interceptor: BallastInterceptor<Inputs, Events, State>
) {
    this.interceptors += interceptor
}

/**
 * Add many [BallastInterceptor]s to the [InputModelConfiguration.Builder].
 */
 operator fun <Inputs : Any, Events : Any, State : Any> InputModelConfiguration.TypedBuilder<Inputs, Events, State>.plusAssign(
    interceptors: Iterable<BallastInterceptor<Inputs, Events, State>>
) {
    this.interceptors += interceptors
}

